-module(trace_manager).
-behaviour(gen_server).

%% gen_server exports
-export([init/1, handle_call/3, handle_info/2, terminate/2]).
%% client interface
-export([start_link/1, register/1, finished/1, pixel/1, start_tracing/0]).

%% Server state record
-record(state, {
            pixbuf,
            free=[],
            workers=[]
       }).


%%
%% Client interface
%%

%% Start tracing
start_tracing() ->
    gen_server:call({global, trace_manager}, start_all).

%% Update a pixel
pixel({X, Y, C}) ->
    gen_server:call({global, trace_manager}, {pixel, {X, Y, C}}).

%% Register as finished
finished(Pid) ->
    gen_server:call({global, trace_manager}, {finished, Pid}).

%% Register a new worker
register(Pid) ->
    gen_server:call({global, trace_manager}, {register_tracer, Pid}).

%% Start the server
start_link(Size) ->
    gen_server:start_link(
        {global, trace_manager},
        trace_manager,
        Size,
        []
    ).

%%
%% gen_server Interface
%%

init(Size) ->
    {ok, BufPid} = gen_server:start_link(pixbuf, Size, []),
    InitialState = #state{pixbuf=BufPid},
    {ok, InitialState}.

%% Set pixel handler
handle_call({pixel, {X, Y, C}}, _From, State=#state{pixbuf=PB}) ->
    pixbuf:update_pixel(PB, {X, Y, C}),
    {reply, ok, State};

%% Finish notification handler
handle_call({finished, Pid}, _From, State=#state{}) ->
    {reply, ok, start_free_workers(add_free_worker(Pid, State))};

%% Register handler
handle_call({register_tracer, Pid}, _From, State=#state{}) ->
    error_logger:info_msg("New tracer process registered at ~p.~n", [Pid]),
    erlang:monitor(process, Pid),
    NewState = add_worker(Pid, State),
    {reply, ok, start_free_workers(add_free_worker(Pid, NewState))};

%% Start all the free registered nodes
handle_call(start_all, _From, State=#state{}) ->
    {reply, ok, start_free_workers(State)}.

%% Info handler
handle_info({'DOWN', _Ref, process, Pid, Reason}, State=#state{}) ->
    error_logger:error_msg("Tracer process ~p down for reason: ~p.~n", [Pid, Reason]),
    {noreply, remove_free_worker(Pid, remove_worker(Pid, State))}.

terminate(shutdown, State=#state{}) ->
    stop_workers(State).

%%
%% Internal functions
%%

%% Start a worker (when there are no failed pixels).
start_worker(Pid, State=#state{pixbuf=PB}) ->
    {X, Y, _} = pixbuf:split_pixel(pixbuf:get_next_pixel(PB)),
    Pos = {X, Y},
    Size = pixbuf:size(PB),
    tracer:start_trace(Pid, Pos, Size),
    remove_free_worker(Pid, State).

%% Start all free workers
start_free_workers(State=#state{free=[]}) -> State;
start_free_workers(State=#state{free=[Pid|Tail]}) ->
    NewState = start_worker(Pid, State#state{free=Tail}),
    start_free_workers(NewState).

%% Stop all workers
stop_workers(#state{workers=[]}) -> ok;
stop_workers(State=#state{workers=[Pid|Rest]}) ->
    tracer:stop(Pid),
    stop_workers(State#state{workers=Rest}).

%% Add a free worker process to the free list
add_free_worker(Pid, State=#state{}) ->
    State#state{free=[Pid|State#state.free]}.

%% Remove a process from the free list
remove_free_worker(Pid, State=#state{}) ->
    State#state{free=lists:delete(Pid, State#state.free)}.

%% Add a worker process to the worker list
add_worker(Pid, State=#state{}) ->
    State#state{workers=[Pid|State#state.workers]}.

%% Remove a worker process from the worker list
remove_worker(Pid, State=#state{}) ->
    State#state{workers=lists:delete(Pid, State#state.workers)}.
    